Skip to content

[scout] extend login functionality with Kibana built-in roles#271992

Draft
dmlemeshko wants to merge 6 commits into
elastic:mainfrom
dmlemeshko:scout/extend-login-with-built-in-role
Draft

[scout] extend login functionality with Kibana built-in roles#271992
dmlemeshko wants to merge 6 commits into
elastic:mainfrom
dmlemeshko:scout/extend-login-with-built-in-role

Conversation

@dmlemeshko
Copy link
Copy Markdown
Contributor

@dmlemeshko dmlemeshko commented May 29, 2026

Summary

Many FTR tests rely on Kibana build-in roles and it makes sense to add its support to Scout. It should simplify FTR -> Scout migration:

UI tests auth flow:

browserAuth.loginWithBuiltinRole('kibana_admin')
  │
  └─ samlAuth.setBuiltinRole('kibana_admin')   ← new helper on SamlAuth (PR)
       │
       ├─ esClient.security.getRole({ name: 'kibana_admin' })
       │    └─ response:
       │       {
       │         kibana_admin: {
       │           cluster: [],
       │           indices: [],
       │           applications: [{ application: 'kibana-.kibana', privileges: ['all'], resources: ['*'] }],
       │           run_as: [],
       │           metadata: { _reserved: true },         ← strip
       │           transient_metadata: { enabled: true }  ← strip
       │         }
       │       }
       │
       ├─ extract ElasticsearchRoleDescriptor:
       │    { cluster, indices, applications, run_as }
       │
       └─ setCustomRole(descriptor)   ← no changes
            ├─ hash check (share session/key across tests in same worker)
            ├─ createElasticsearchCustomRole(esClient, 'custom_role_worker_1', descriptor)
            │    └─ PUT /_security/role/custom_role_worker_1  { ...kibana_admin descriptor }
            └─ customRoleHash = hash(descriptor)

  └─ loginAs(samlAuth.customRoleName)   ← no changes
       └─ [local or cloud, exact same path as loginWithCustomRole]

API tests auth flow:

requestAuth.getApiKeyForBuiltinRole('kibana_admin')
  │
  └─ samlAuth.setBuiltinRole('kibana_admin')        ← SamlAuth (PR)
       │
       ├─ esClient.security.getRole({ name: 'kibana_admin' })
       │    └─ response:
       │       {
       │         kibana_admin: {
       │           cluster: [],
       │           indices: [],
       │           applications: [{ application: 'kibana-.kibana', privileges: ['all'], resources: ['*'] }],
       │           run_as: [],
       │           metadata: { _reserved: true },         ← strip
       │           transient_metadata: { enabled: true }  ← strip
       │         }
       │       }
       │
       └─ setCustomRole(descriptor)                  ←  unchanged
            ├─ hash-dedup check (reuses cached role within same worker)
            ├─ PUT /_security/role/custom_role_worker_1 { ...descriptor }
            └─ returns descriptor to caller            ← new: returned for reuse
  │
  └─ createApiKeyWithAdminCredentials(
         roleName  = 'custom_role_worker_1',
         descriptors = { custom_role_worker_1: descriptor }   ← no second ES fetch
     )
       │
       ├─ samlAuth.session.getApiCredentialsForRole('admin')  ← admin cookie header
       │
       └─ POST /internal/security/api_key
              body: {
                name: 'myTestApiKey-0-custom_role_worker_1-worker-1',
                role_descriptors: {
                  custom_role_worker_1: {
                    cluster: [],
                    indices: [],
                    applications: [{ application: 'kibana-.kibana', privileges: ['all'], resources: ['*'] }],
                    run_as: []
                  }
                }
              }
              └─ { id, name, api_key, encoded }
                   └─ { apiKey, apiKeyHeader: { Authorization: 'ApiKey <encoded>' } }

Note: since build-in roles are available only in stateful, the error will be thrown when you call it in tests running serverless env

@infra-vault-gh-plugin-prod
Copy link
Copy Markdown

infra-vault-gh-plugin-prod Bot commented May 29, 2026

🤖 Jobs for this PR can be triggered through checkboxes. 🚧

ℹ️ To trigger the CI, please tick the checkbox below 👇

  • Click to trigger kibana-pull-request for this PR!
  • Click to trigger kibana-deploy-project-from-pr for this PR!
  • Click to trigger kibana-deploy-cloud-from-pr for this PR!
  • Click to trigger kibana-entity-store-performance-from-pr for this PR!
  • Click to trigger kibana-storybooks-from-pr for this PR!

@dmlemeshko dmlemeshko added release_note:skip Skip the PR/issue when compiling release notes backport:all-open Backport to all branches that could still receive a release labels May 29, 2026
@dmlemeshko dmlemeshko added the reviewer:claude PR review and comments with Claude label Jun 1, 2026
Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review notes on the Scout built-in role support:

  • getApiKeyForBuiltinRole provisions a custom_role_worker_N ES role that the API key never uses (the descriptor is embedded inline). For API-only consumers this is an extra PUT /_security/role per call plus cleanup overhead.
  • samlAuth.setBuiltinRole only strips metadata / transient_metadata / description; ES roles can carry other fields (notably global) that the role API accepts but the API key role_descriptors endpoint rejects. An allow-list of supported descriptor fields would be more forward-compatible.
  • New browserAuth.loginWithBuiltinRole is not exercised by the new built_in_roles.spec.ts; the API/SAML methods are covered but the UI fixture path is not.

Non-blocking — leaving comments inline for context.

Generated by Claude Reviewer for issue #271992 · ● 12.1M

Comment on lines +219 to +224
const getApiKeyForBuiltinRole = async (roleName: string): Promise<RoleApiCredentials> => {
const descriptor = await samlAuth.setBuiltinRole(roleName);
return createApiKeyWithAdminCredentials(samlAuth.customRoleName, {
[samlAuth.customRoleName]: descriptor,
});
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setBuiltinRole calls setCustomRole internally, which performs a PUT /_security/role/custom_role_worker_N to provision the role in ES (and a corresponding DELETE at worker cleanup). However, this ES role is never used here — the API key embeds the role_descriptors inline. For API-only consumers, this is an unnecessary round trip per call (and per worker, an unnecessary cleanup).

Consider exposing a lower-level helper that just fetches and strips the descriptor (without the setCustomRole side effect), and only provision the role lazily when login/SAML cookies are actually needed. The current behavior is correct, just wasteful when only API keys are required.

expect(apiKey.name).toBeDefined();
expect(apiKeyHeader.Authorization).toMatch(/^ApiKey /);
}
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file covers samlAuth.setBuiltinRole and requestAuth.getApiKeyForBuiltinRole, but there is no test for browserAuth.loginWithBuiltinRole, which is the third new public method introduced by this PR. Since it has its own user-facing contract (returns a cookie via loginAs(samlAuth.customRoleName) after setBuiltinRole), a small UI test would close the coverage gap and protect the browser-auth path against regressions.

Comment on lines +88 to +93
const {
metadata: _metadata,
transient_metadata: _transient,
description: _description,
...descriptor
} = roleData;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The stripping list only removes metadata, transient_metadata, and description. ES getRole can return additional fields (e.g. global, remote_cluster, remote_indices) depending on the role. global in particular is accepted by the role-management API but is not accepted by the API key role_descriptors endpoint, so for any built-in role that defines a global block (or future roles that add one), getApiKeyForBuiltinRole will fail with an opaque 400.

Consider either (a) allow-listing the known-supported descriptor fields (cluster, indices, applications, run_as, remote_cluster, remote_indices) instead of deny-listing, or (b) documenting the limitation so callers know which roles are safe to pass through. Option (a) makes the surface forward-compatible with new ES role fields.

@kibanamachine
Copy link
Copy Markdown
Contributor

kibanamachine commented Jun 1, 2026

⏳ Build in-progress, with failures

Failed CI Steps

Test Failures

  • [job] [logs] FTR Configs #97 / Entity Analytics - Risk Score Maintainer @ess @serverless @serverlessQA Risk Score Maintainer Resolution Scoring with test log data aggregates alerts from both target and alias into a single resolved score
  • [job] [logs] Scout Lane #48 - serverless-observability_complete / default / local-serverless-observability_complete - SAML Auth fixture - getApiKeyForBuiltinRole should create an API key scoped to a built-in ES role
  • [job] [logs] Scout Lane #48 - serverless-observability_complete / default / local-serverless-observability_complete - SAML Auth fixture - getApiKeyForBuiltinRole should create an API key scoped to a built-in ES role
  • [job] [logs] Scout Lane #48 - serverless-observability_complete / default / local-serverless-observability_complete - SAML Auth fixture - setBuiltinRole should provision the custom role slot and return the descriptor
  • [job] [logs] Scout Lane #48 - serverless-observability_complete / default / local-serverless-observability_complete - SAML Auth fixture - setBuiltinRole should provision the custom role slot and return the descriptor
  • [job] [logs] Scout Lane #53 - serverless-observability_logs_essentials / default / local-serverless-observability_logs_essentials - SAML Auth fixture - getApiKeyForBuiltinRole should create an API key scoped to a built-in ES role
  • [job] [logs] Scout Lane #53 - serverless-observability_logs_essentials / default / local-serverless-observability_logs_essentials - SAML Auth fixture - getApiKeyForBuiltinRole should create an API key scoped to a built-in ES role
  • [job] [logs] Scout Lane #53 - serverless-observability_logs_essentials / default / local-serverless-observability_logs_essentials - SAML Auth fixture - setBuiltinRole should provision the custom role slot and return the descriptor
  • [job] [logs] Scout Lane #53 - serverless-observability_logs_essentials / default / local-serverless-observability_logs_essentials - SAML Auth fixture - setBuiltinRole should provision the custom role slot and return the descriptor
  • [job] [logs] Scout Lane #34 - serverless-search / default / local-serverless-search - SAML Auth fixture - getApiKeyForBuiltinRole should create an API key scoped to a built-in ES role
  • [job] [logs] Scout Lane #34 - serverless-search / default / local-serverless-search - SAML Auth fixture - setBuiltinRole should provision the custom role slot and return the descriptor
  • [job] [logs] Scout Lane #59 - serverless-security_complete / default / local-serverless-security_complete - SAML Auth fixture - getApiKeyForBuiltinRole should create an API key scoped to a built-in ES role
  • [job] [logs] Scout Lane #59 - serverless-security_complete / default / local-serverless-security_complete - SAML Auth fixture - getApiKeyForBuiltinRole should create an API key scoped to a built-in ES role
  • [job] [logs] Scout Lane #59 - serverless-security_complete / default / local-serverless-security_complete - SAML Auth fixture - setBuiltinRole should provision the custom role slot and return the descriptor
  • [job] [logs] Scout Lane #59 - serverless-security_complete / default / local-serverless-security_complete - SAML Auth fixture - setBuiltinRole should provision the custom role slot and return the descriptor
  • [job] [logs] Scout Lane #12 - stateful-classic / default / local-stateful-classic - Built-in ES roles - setBuiltinRole should throw when called on a Serverless deployment
  • [job] [logs] Scout Lane #12 - stateful-classic / default / local-stateful-classic - Built-in ES roles - setBuiltinRole should throw when called on a Serverless deployment
  • [job] [logs] Scout Lane #12 - stateful-classic / default / local-stateful-classic - Built-in ES roles - setBuiltinRole should throw when the role does not exist in Elasticsearch
  • [job] [logs] Scout Lane #12 - stateful-classic / default / local-stateful-classic - Built-in ES roles - setBuiltinRole should throw when the role does not exist in Elasticsearch
  • [job] [logs] Scout Lane #7 - stateful-classic / default / local-stateful-classic - Entity analytics management page - Privileges - should show privileges callout and disable switch for user without risk engine privileges
  • [job] [logs] Scout Lane #7 - stateful-classic / default / local-stateful-classic - Entity analytics management page - Risk Score tab - should discard changes when clicking discard button
  • [job] [logs] Scout Lane #7 - stateful-classic / default / local-stateful-classic - Entity analytics management page - Risk Score tab - should show save bar when toggling closed alerts switch
  • [job] [logs] Scout Lane #7 - stateful-classic / default / local-stateful-classic - Entity analytics management page - Risk Score tab - should show save bar when toggling retain checkbox
  • [job] [logs] Scout Lane #2 - stateful-classic / default / local-stateful-classic - Query streams - Edit query stream - should support editing an existing query stream's ES|QL query from the partitioning tab

History

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport:all-open Backport to all branches that could still receive a release release_note:skip Skip the PR/issue when compiling release notes reviewer:claude PR review and comments with Claude

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants